/*
 * @(#)Parameters.java     14/08/02
 * 
 * Author             Rick Wilson
 * Copyright (c) 2014 Rick Wilson
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of 'Taareekah' nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */



package parameters;

//~--- non-JDK imports --------------------------------------------------------

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

//~--- JDK imports ------------------------------------------------------------

import java.io.File;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

/**
 * Loading and saving of a parameters data tree.
 * @author rick
 */
public class Parameters {

    /** Field description */
    private static Map<String, Parameters>	parameter_sets;

    /** Field description */
    private ParameterGroup	parameters_root;

    /**
     * Doesn't do anything.
     */
    public Parameters() {}

    /**
     * Returns a Parameters with the data from file 'file' but doesn't store it in parameter_sets.
     * @param file The filename to read data from.
     * @return A Parameters with the data from file.
     * @throws Exception If parseDefinitionFile fails.
     */
    public static Parameters loadParameterSetWithoutStoring(String file) throws Exception {
	Parameters	params	= new Parameters();

	params.parseDefinitionFile(file);

	return params;
    }

    /**
     * Returns a Parameters with the data from file 'file' and stores it under key 'key'.
     * @param key The key to save this Parameters under.
     * @param parameter_file The file to load data from.
     * @return A new Parameters.
     * @throws Exception If parseDefinitionFile fails.
     */
    public static Parameters createParameterSet(String key, String parameter_file) throws Exception {
	if (parameter_sets == null) {
	    parameter_sets	= new HashMap<>();
	}

	if (!parameter_sets.containsKey(key)) {
	    Parameters	params	= new Parameters();

	    parameter_sets.put(key, params);
	}

	parameter_sets.get(key).parseDefinitionFile(parameter_file);

	return parameter_sets.get(key);
    }

    /**
     * Returns the Parameters stored under key 'key'.
     * @param key The key.
     * @return The Parameters or null.
     * @throws Exception
     */
    public static Parameters getParameterSet(String key) throws Exception {
	return parameter_sets.get(key);
    }

    /**
     * Method description
     *
     *
     * @param node
     *
     * @return
     *
     * @throws Exception
     */
    private Parameter createParameterFromXML(Node node) throws Exception {
	Element	e	= (Element) node;
	String	type	= "";

	if (e.hasAttribute("type")) {
	    type	= e.getAttribute("type");
	}

	return new Parameter(e.getAttribute("key"), e.getAttribute("value"), type);
    }

    /**
     * Method description
     *
     *
     * @param node
     *
     * @return
     *
     * @throws Exception
     */
    private ParameterNode createParameterNodeFromXML(Node node) throws Exception {
	if ("parameter".equals(node.getNodeName())) {
	    return createParameterFromXML(node);
	} else if ("parametergroup".equals(node.getNodeName())) {
	    return createParameterGroupFromXML(node);
	}

	throw new Exception("Node not a parameter of parametergroup");
    }

    /**
     * Method description
     *
     *
     * @param node
     *
     * @return
     *
     * @throws Exception
     */
    private ParameterGroup createParameterGroupFromXML(Node node) throws Exception {
	Element	e	= (Element) node;
	String	type	= "";

	if (e.hasAttribute("type")) {
	    type	= e.getAttribute("type");
	}

	ParameterGroup	group			= new ParameterGroup(e.getAttribute("key"), type);
	NodeList	child_parameter_nodes	= node.getChildNodes();

	for (int i = 0; i < child_parameter_nodes.getLength(); i++) {
	    if (child_parameter_nodes.item(i).getNodeType() != Node.ELEMENT_NODE) {
		continue;
	    }

	    group.addChild(createParameterNodeFromXML(child_parameter_nodes.item(i)));
	}

	NamedNodeMap	attributes	= node.getAttributes();

	for (int i = 0; i < attributes.getLength(); i++) {
	    Node	attr_node	= attributes.item(i);

	    if ("key".equals(attr_node.getNodeName()) || "type".equals(attr_node.getNodeName())) {
		continue;
	    }

	    Parameter	param	= new Parameter(attr_node.getNodeName(), attr_node.getNodeValue(), "");

	    group.addChild(param);
	}

	return group;
    }

    /**
     * Loads data from the file parameter_definition_file.
     * @param parameter_definition_file The filename to load
     * @return Always true.
     * @throws Exception
     */
    public boolean parseDefinitionFile(String parameter_definition_file) throws Exception {
	if (parameters_root != null) {
	    parameters_root	= null;
	}

	Document	doc	= null;

	try {
	    File			fXmlFile	= new File(parameter_definition_file);
	    DocumentBuilderFactory	dbFactory	= DocumentBuilderFactory.newInstance();
	    DocumentBuilder		dBuilder	= dbFactory.newDocumentBuilder();

	    doc	= dBuilder.parse(fXmlFile);
	    doc.getDocumentElement().normalize();
	} catch (Exception e) {
	    Logger.getLogger(Parameters.class.getName()).log(Level.SEVERE, "Couldnt load from xml file", e);

	    throw e;
	}

	try {
	    Node	root_node	= doc.getFirstChild();
	    Node	root_group_node	= doc.getElementsByTagName("parametergroup").item(0);

	    parameters_root	= createParameterGroupFromXML(root_group_node);
	} catch (Exception e) {
	    Logger.getLogger(Parameters.class.getName()).log(Level.SEVERE, "Couldnt parse xml file", e);

	    throw e;
	}

	return true;
    }

    /**
     * Saves this Parameters as XML.
     * @param filename The filename of the XML file.
     * @return true
     * @throws Exception If parameters_root.createXMLElement fails.
     */
    public boolean save(String filename) throws Exception {
	DocumentBuilderFactory	doc_factory	= DocumentBuilderFactory.newInstance();
	DocumentBuilder		doc_builder	= doc_factory.newDocumentBuilder();
	Document		doc		= doc_builder.newDocument();
	Element			root		= doc.createElement("parametersdefinition");

	doc.appendChild(root);
	root.appendChild(parameters_root.createXMLElement(doc));

	TransformerFactory	transformer_factory	= TransformerFactory.newInstance();
	Transformer		transformer		= transformer_factory.newTransformer();
	DOMSource		source			= new DOMSource(doc);
	StreamResult		result			= new StreamResult(new File(filename));

	transformer.transform(source, result);

	return true;
    }

    /**
     *
     * @return
     */
    public Set<ParameterNode> getAllUnset() {
	return parameters_root.getAllUnsetNodes();
    }

    /**
     *
     * @return
     */
    public boolean isSet() {
	return parameters_root.isSet();
    }

    /**
     *
     * @param key
     * @return
     * @throws Exception
     */
    public ParameterNode getParameterNode(String key) throws Exception {
	if ("".equals(key)) {
	    return parameters_root;
	}

	return parameters_root.getChild(key);
    }

    /**
     *
     * @param name
     * @return
     */
    public boolean hasParameter(String name) {
	return parameters_root.hasChild(name);
    }

    /**
     *
     * @param name
     * @return
     * @throws Exception
     */
    public String getParameterValueAsString(String name) throws Exception {
	try {
	    Parameter	param	= (Parameter) parameters_root.getChild(name);

	    if (!param.isSet()) {
		Logger.getLogger(Parameters.class.getName()).log(Level.SEVERE,
				 "Couldnt getParameterValueAsString {0}", name);
	    }

	    return param.getValueAsString();
	} catch (Exception e) {
	    Logger.getLogger(Parameters.class.getName()).log(Level.SEVERE,
			     "Couldnt getParameterValueAsString {0}", name);

	    throw e;
	}
    }
}


//~ Formatted by Jindent --- http://www.jindent.com
